-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local skynet = require 'skynet'
local class = require 'mc.class'
local log = require 'mc.logging'
local m_error_custom = require 'messages.custom'
local m_enums = require 'types.enums'
local cq = require 'chain_queue'

local fructrl_proxy = class()

local function check_action_equal(a, b)
    if a.system_id == b.system_id then
        return true
    end
    return false
end

local function make_action(system_id, op_cause, ctx, op_type)
    return {
        system_id = system_id,
        op_cause = op_cause,
        ctx = ctx,
        -- 仅用于区分normal power cycle / force power cycle
        op_type = op_type
    }
end

function fructrl_proxy:ctor(object, multihost_obj, host_objs)
    self.chassis_fructrl_obj = object
    self.staggered_poweron_interval = object.StaggeredPowerOnInterval
    self.multihost_obj = multihost_obj
    self.host_objs = host_objs
    self.power_on_cq = cq.new(check_action_equal)
    self.restart_cq = cq.new(check_action_equal)
    self.cycle_cq = cq.new(check_action_equal)
end

function fructrl_proxy:all_host_power_on(ctx, RestartCause)
    for i = 1, self.multihost_obj:get_host_count() do
        self.power_on_cq:push(make_action(i, RestartCause, ctx))
        log:mass("[fructrl proxy] host %s push power on to queue", i)
    end
    self:create_power_on_task()
end

function fructrl_proxy:one_host_power_on(system_id, RestartCause, ctx)
    self.power_on_cq:push(make_action(system_id, RestartCause, ctx))
    self:create_power_on_task()
end

function fructrl_proxy:all_host_restart(ctx, RestartCause)
    for i = 1, self.multihost_obj:get_host_count() do
        self.restart_cq:push(make_action(i, RestartCause, ctx))
        log:mass("[fructrl proxy] host %s push restart to queue, cause: %s", i, RestartCause)
    end
    self:create_restart_task()
end

function fructrl_proxy:one_host_restart(system_id, ctx, RestartCause)
    self.restart_cq:push(make_action(system_id, RestartCause, ctx))
    self:create_restart_task()
end

function fructrl_proxy:all_host_power_off(ctx, RestartCause, type)
    for i = 1, self.multihost_obj:get_host_count() do
        -- 先将当前host等待的power on 和 restart在队列中删除
        self.power_on_cq:delete({system_id = i})
        self.restart_cq:delete({system_id = i})
        self.cycle_cq:delete({system_id = i})
        log:mass("[fructrl proxy] host %s delete poweron and restart from queue, cause: %s", i)

        if self.host_objs[i] and self.host_objs[i].powerapi then
            self.host_objs[i].powerapi:system_reset(ctx, type, RestartCause)
            log:debug("[chassis power ctrl] host %s push power off, cause: %s", i, RestartCause)
        else
            log:error("[chassis power ctrl] host %s power off failed ", i)
        end
    end
end

function fructrl_proxy:one_host_power_off(system_id, ctx, power_off_type, RestartCause)
    self.power_on_cq:delete({system_id = system_id})
    self.restart_cq:delete({system_id = system_id})
    self.cycle_cq:delete({system_id = system_id})
    if not self.host_objs[system_id] or not self.host_objs[system_id].powerapi then
        error(m_error_custom.ActionNotSupported('%OperateType'))
    end
    self.host_objs[system_id].powerapi:system_reset(ctx, power_off_type, RestartCause)
    log:debug("[fructrl proxy] host %s call graceful shutdown, cause: %s", system_id, RestartCause)
end

function fructrl_proxy:all_host_nmi(ctx, RestartCause)
    for i = 1, self.multihost_obj:get_host_count() do
        if self.host_objs[i] and self.host_objs[i] then
            self.host_objs[i].powerapi:system_reset(ctx, m_enums.PowerCtrlType.Nmi, RestartCause)
            log:debug("[fructrl proxy] host %s call nmi, cause: %s", i, RestartCause)
        end
    end
end

function fructrl_proxy:one_host_nmi(system_id, ctx, RestartCause)
    if not self.host_objs[system_id] or not self.host_objs[system_id].powerapi then
        error(m_error_custom.ActionNotSupported('%OperateType'))
    end
    self.host_objs[system_id].powerapi:system_reset(ctx, m_enums.PowerCtrlType.Nmi, RestartCause)
end

function fructrl_proxy:all_host_set_poweron_lock(PwrOnLocked, Timeout, AppName, Reason)
    for i = 1, self.multihost_obj:get_host_count() do
        if self.host_objs[i] and self.host_objs[i].fructrl and self.host_objs[i].fructrl.power_on_lock then
            self.host_objs[i].fructrl.power_on_lock:poweron_lock_entrance(PwrOnLocked, Timeout, AppName, Reason)
            log:debug("[fructrl proxy] host %s set poweron lock, pwronlocked=%s, timeout=%d, appname=%s, reason=%s",
                i, PwrOnLocked, Timeout, AppName, Reason)
        end
    end
    return 0
end

function fructrl_proxy:all_host_set_poweron_strategy(stragety)
    for i = 1, self.multihost_obj:get_host_count() do
        if self.host_objs[i] and self.host_objs[i].fructrl then
            self.host_objs[i].fructrl:set_PowerOnStrategy(stragety)
            log:debug("[fructrl proxy] host %s set poweron strategy to %s", i, stragety)
        end
    end
    return 0
end

function fructrl_proxy:all_host_set_poweron_strategy_exceptions(Reason, Execute, EffectivePeriod, Priority)
    for i = 1, self.multihost_obj:get_host_count() do
        if self.host_objs[i] and self.host_objs[i].pwr_restore then
            self.host_objs[i].pwr_restore:update_pwr_restore(Reason, Execute, EffectivePeriod, tostring(Priority))
            log:debug("[fructrl proxy] host %s set poweron strategy exceptions to %s, period %s, priority %s",
                i, tostring(Execute), EffectivePeriod, Priority)
        end
    end
    return 0
end

function fructrl_proxy:one_host_cycle(system_id, ctx, RestartCause, operate_type)
    self.cycle_cq:push(make_action(system_id, RestartCause, ctx, operate_type))
    self:create_cycle_task()
end

local function call_powerapi_method(self, power_action, operate_type)
    local ok, ret = pcall(function ()
        self.host_objs[power_action.system_id].powerapi:system_reset(
            power_action.ctx, operate_type, power_action.op_cause)
    end)
    if not ok then
        log:error("[fructrl proxy] host %s call %s failed, ret: %s",power_action.system_id, operate_type, ret)
    end
end

function fructrl_proxy:create_power_on_task()
    if self.power_on_task then
        log:notice("[fructrl proxy] duplicate power on task")
        return
    end
    self.power_on_task = true

    skynet.fork_once(function()
        local power_action, has_powerapi
        while true do
            power_action = self.power_on_cq:pop()
            if not power_action then
                self.power_on_task = false
                log:notice("[fructrl proxy] power on queue is empty")
                return
            end
            -- 对应host上电操作
            has_powerapi = self.host_objs[power_action.system_id] and self.host_objs[power_action.system_id].powerapi
            if not has_powerapi then
                log:error("[fructrl proxy] host %s power on failed", power_action.system_id)
            elseif has_powerapi and not self.host_objs[power_action.system_id].fructrl:get_PwrOnLocked() then
                call_powerapi_method(self, power_action, m_enums.PowerCtrlType.On)
                log:debug("[fructrl proxy] host %s call power on, cause: %s", power_action.system_id,
                    power_action.op_cause)
                skynet.sleep(self.staggered_poweron_interval * 100)
            else
                self.power_on_cq:push(power_action)
                log:debug("[fructrl proxy] host %s will try again due to power on lock, cause: %s",
                    power_action.system_id, power_action.op_cause)
                skynet.sleep(self.staggered_poweron_interval * 100)
            end
        end
    end)
end

function fructrl_proxy:create_restart_task()
    if self.restart_process_task then
        log:notice("[fructrl proxy] duplicate power restart task")
        return
    end
    self.restart_process_task = true

    skynet.fork_once(function()
        local power_action
        while true do
            power_action = self.restart_cq:pop()
            if not power_action then
                self.restart_process_task = false
                log:notice("[fructrl proxy] restart on queue is empty")
                return
            end
            -- 对应host restart操作
            if self.host_objs[power_action.system_id] and self.host_objs[power_action.system_id].powerapi then
                call_powerapi_method(self, power_action, m_enums.PowerCtrlType.ForceRestart)
                log:debug("[fructrl proxy] host %s call restart, cause: %s", power_action.system_id,
                    power_action.op_cause)
                skynet.sleep(self.staggered_poweron_interval * 100)
            else
                log:error("[fructrl proxy] host %s restart failed ", power_action.system_id)
            end
        end
    end)
end

function fructrl_proxy:create_cycle_task()
    if self.cycle_process_task then
        log:notice("[fructrl proxy] duplicate power cycle task")
        return
    end
    self.cycle_process_task = true

    skynet.fork_once(function()
        local power_action
        while true do
            power_action = self.cycle_cq:pop()
            if not power_action then
                self.cycle_process_task = false
                log:notice("[fructrl proxy] cycle queue is empty")
                return
            end
            -- 对应host cycle操作
            if self.host_objs[power_action.system_id] and self.host_objs[power_action.system_id].powerapi then
                call_powerapi_method(self, power_action, power_action.op_type)
                log:debug("[fructrl proxy] host %s call cycle, cause: %s", power_action.system_id,
                    power_action.op_cause)
                skynet.sleep(self.staggered_poweron_interval * 100)
            else
                log:error("[fructrl proxy] host %s %s failed ", power_action.system_id,
                    power_action.type == m_enums.PowerCtrlType.ForcePowerCycle and
                    m_enums.PowerCtrlType.ForcePowerCycle or m_enums.PowerCtrlType.PowerCycle)
            end
        end
    end)
end

function fructrl_proxy:wait_all_host_power_on_finished()
    local times = self.staggered_poweron_interval * self.multihost_obj:get_host_count()
    for _= 1, times do
        if self.power_on_cq:get_len() == 0 then
            log:notice("[fructrl proxy] all host power on finished")
            return
        end
        skynet.sleep(100)
    end
    log:notice("[fructrl proxy] all host power on not finished")
end

return fructrl_proxy